package com.study.sbsummary.utils;

import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class ReflectionUtil {
    private static final Map<String, Map<String, Field>> CLS_FIELDS =  new HashMap<>();

    public static Map fields(Class cls) {
        return fields(cls, false, false);
    }

    public static Map fields(Class cls, boolean includeSuper) {
        return fields(cls, includeSuper, false);
    }

    /**
     * 要获指定类的属性和Field的映射关系（可能含父类的属性、通过注解映射名称的属性），目前有将映射关系保存到内存里，所以此类
     * 映射不应该太多，否则可能占用内存（而且当前简单期间，参数不同时存储映射关系是被当做不同的映射关系存储的，即key为FieldClass.toString()）。
     * @param cls 要获取属性和Field的映射关系的class
     * @param includeSuper 是否映射父类的属性和字段
     * @param includeAnnotation 是否同时映射对应属性的注解（注解映射属性名称）
     * @return
     */
    public static Map fields(Class cls, boolean includeSuper, boolean includeAnnotation) {
        FieldClass fieldClass = new FieldClass(cls, includeSuper, includeAnnotation);
        Map fields = CLS_FIELDS.get(fieldClass.key());
        if (fields != null) {
            return fields;
        }
        synchronized (cls.getCanonicalName()) {
            fields = CLS_FIELDS.get(cls);
            if (fields == null) {
                fields = new HashMap();
                CLS_FIELDS.put(fieldClass.key(), fields);
                Class tempClass = cls;
                do {
                    Field[] declaredField = tempClass.getDeclaredFields();
                    for (Field field : declaredField) {
                        field.setAccessible(true);
                        fields.put(field.getName(), field);
                        if (includeAnnotation) {
                            extractAnnotations(cls, fields, field);
                        }
                    }
                    if (includeAnnotation) {
                        extractMyBatisEntityColumn(cls, fields);
                    }
                    if (includeSuper) {
                        tempClass = tempClass.getSuperclass();
                    }
                } while (includeSuper && tempClass != null);
            }
        }
        return fields;
    }

    /**
     * 解析并映射mybatis通过Enum Column映射字段后的Field映射关系
     * TODO:可能会存在问题：因为类的属性是先从处理当前类，然后再处理父类，如果当Column里含有父类的属性名称映射关系的时候，
     * 通过Column映射父类属性的对应关系可能会丢掉，除非父类也定义了对应的Column映射关系。
     * @param cls
     * @param fields
     */
    private static void extractMyBatisEntityColumn(Class cls, Map<String, Field> fields) {
        Class[] innerClasses = cls.getDeclaredClasses();
        if (innerClasses == null) {
            return;
        }
        for (Class innerCls : innerClasses) {
            if (innerCls.isEnum() && innerCls.getCanonicalName().equals(cls.getCanonicalName() + ".Column")) {
                try {
                    Method method = innerCls.getMethod("value") != null?innerCls.getMethod("value") : innerCls.getMethod("getValue");
                    if (method == null) {
                        continue;
                    }
                    Object[] enums = innerCls.getEnumConstants();
                    if (enums == null) {
                        continue;
                    }
                    /*Field columnField = innerCls.getDeclaredField("column");
                    columnField.setAccessible(true);*/
                    for (Object one : enums) {
                        String propertyName = one.toString();
                        String tblColumn = (String) method.invoke(one);
                        if (fields.containsKey(propertyName) && !fields.containsKey(tblColumn)) {
                            fields.put(tblColumn, fields.get(propertyName));
                        }
                    }
                } catch (Exception e) {
                    log.warn(e.getMessage());
                }
            }
        }
    }

    /**
     * 解析通过注解映射字段和类的Field的映射关系
     * @param cls
     * @param fields
     * @param field
     */
    private static void extractAnnotations(Class cls, Map<String, Field> fields, Field field) {
        Annotation[] annotations = field.getAnnotations();
        if (annotations == null) {
            return;
        }
        for (Annotation annotation : annotations) {
            if (annotation instanceof org.springframework.data.mongodb.core.mapping.Field) {
                org.springframework.data.mongodb.core.mapping.Field mongoField =
                        (org.springframework.data.mongodb.core.mapping.Field)annotation;
                fields.put(mongoField.value(), field);
            } else if (annotation instanceof JsonProperty) {
                JsonProperty jsonProperty = (JsonProperty) annotation;
                fields.put(jsonProperty.value(), field);
            } else if (annotation instanceof TableField) {
                TableField tableField = (TableField) annotation;
                fields.put(tableField.value(), field);
            }
        }
    }

    public static Object getValue(Object object, Field field) {
        try {
            return field.get(object);
        } catch (IllegalAccessException e) {
            log.error("failed to get {} value of class {}, due to {}", field.getName(), object.getClass().getName(),
                    e.getMessage());
            return null;
        }
    }

    @Data
    @AllArgsConstructor
    private static class FieldClass {
        private Class cls;
        private boolean includeSuper;
        private boolean includeAnnotation;

        @Override
        public String toString() {
            return key();
        }

        public String key() {
            return cls.getCanonicalName() + ":" + includeSuper + ":" + includeAnnotation;
        }
    }
}
